package falcon.script;

import java.util.ArrayList;
import java.util.Stack;

import falcon.common.Errno;
import falcon.common.Log;
import falcon.script.FalconLangToken.Keyword;
import falcon.script.FalconLangToken.Operator;
import falcon.script.FalconLangToken.Symbol;

public final class FalconLangSyntacticAnalyzer {
	
	public FalconLangSyntacticAnalyzer() {
		m_stack = new Stack<Integer>();
	}
	
	public void reset() {
		m_x = 0;
		m_stack.clear();
	}

	public int analyze(ArrayList<FalconLangToken> tokenList, ArrayList<FalconLangAst> astList) {
		m_tokenList = tokenList;
		int errno = Errno.SUCCESS;
		while (peek() != null) {
			FalconLangAst ast = new FalconLangAst();
			errno = process(ast);
			if (errno != Errno.SUCCESS) {
				break;
			}
			if (astList != null) {
				astList.add(ast);
			}
		}
		return errno;
	}
	
	private int process(FalconLangAst ast) {
		if (check(0, FalconLangToken.KEYWORD, Keyword.FUNC)) {
			next();
			ast.type = FalconLangAst.INVOKE_FUNC;
			return processInvoke(ast);
		} else if (check(0, FalconLangToken.KEYWORD, Keyword.FLOW)) {
			next();
			ast.type = FalconLangAst.INVOKE_FLOW;
			return processInvoke(ast);
		} else if (check(0, FalconLangToken.KEYWORD, Keyword.SYS)) {
			next();
			ast.type = FalconLangAst.INVOKE_SYS;
			return processInvoke(ast);
		} else if (check(0, FalconLangToken.KEYWORD, Keyword.IF)) {
			next();
			ast.type = FalconLangAst.CONDITION_IF;
			if (!checkIfBlock(ast.type)) {
				return Errno.SCRIPT_SYNTAX_ERROR;
			}
			return processCondition(ast);
		} else if (check(0, FalconLangToken.KEYWORD, Keyword.ELIF)) {
			next();
			ast.type = FalconLangAst.CONDITION_ELIF;
			if (!checkIfBlock(ast.type)) {
				return Errno.SCRIPT_SYNTAX_ERROR;
			}
			return processCondition(ast);
		} else if (check(0, FalconLangToken.KEYWORD, Keyword.ELSE)) {
			next();
			ast.type = FalconLangAst.CONDITION_ELSE;
			if (!checkIfBlock(ast.type)) {
				return Errno.SCRIPT_SYNTAX_ERROR;
			}
			return Errno.SUCCESS;
		} else if (check(0, FalconLangToken.KEYWORD, Keyword.END)) {
			next();
			ast.type = FalconLangAst.CONDITION_END;
			if (!checkIfBlock(ast.type)) {
				return Errno.SCRIPT_SYNTAX_ERROR;
			}
			return Errno.SUCCESS;
		} else if (check(0, FalconLangToken.KEYWORD, Keyword.REUTRN)) {
			next();
			ast.type = FalconLangAst.RET;
			return processReture(ast);
		} else if (check(0, FalconLangToken.IDENT, null)) {
			return processAssign(ast);
		}
		Log.log("");
		return Errno.SCRIPT_SYNTAX_ERROR;
	}
	
	/**
	 * [assign] -> [var] = func [invoke]<br>
	 * [assign] -> [var] = flow [invoke]<br>
	 * [assign] -> [var] = sys [invoke]<br>
	 * [assign] -> [var] = [val]<br>
	 */
	private int processAssign(FalconLangAst ast) {
		ArrayList<FalconLangAst> dstList = new ArrayList<FalconLangAst>();
		int errno = processVar(dstList);
		if (errno != Errno.SUCCESS) {
			Log.log("");
			return errno;
		}
		if (!check(0, FalconLangToken.OPERATOR, Operator.ASSIGN)) {
			Log.log("");
			return Errno.SCRIPT_SYNTAX_ERROR;
		}
		ArrayList<FalconLangAst> srcList = null;
		FalconLangAst invoke = null;
		if (check(1, FalconLangToken.KEYWORD, Keyword.FUNC)) {
			invoke = new FalconLangAst(FalconLangAst.INVOKE_FUNC);
		} else if (check(1, FalconLangToken.KEYWORD, Keyword.FLOW)) {
			invoke = new FalconLangAst(FalconLangAst.INVOKE_FLOW);
		} else if (check(1, FalconLangToken.KEYWORD, Keyword.SYS)) {
			invoke = new FalconLangAst(FalconLangAst.INVOKE_SYS);
		}
		if (invoke != null) {
			move(2);
			errno = processInvoke(invoke);
		} else {
			next();
			srcList = new ArrayList<FalconLangAst>();
			errno = processVal(srcList);
		}
		if (errno != Errno.SUCCESS) {
			Log.log("");
			return errno;
		}
		if (srcList != null && dstList.size() != srcList.size()) {
			Log.log("");
			return Errno.SCRIPT_SYNTAX_ERROR;
		}
		if (invoke != null && dstList.size() > FalconLangProcessor.INVOKE_RET_COUNT) {
			Log.log("");
			return Errno.SCRIPT_SYNTAX_ERROR;
		}
		ArrayList<FalconLangAst> list = new ArrayList<FalconLangAst>();
		// 简化抽象语法树
		// 将 x, y, z = func.name(a, b, c)
		// 简化为
		// func.name(a, b, c)
		// x = t1
		// y = t2
		// z = t3
		if (invoke != null) {
			FalconLangAst seq = new FalconLangAst(FalconLangAst.SEQ);
			seq.child1 = invoke;
			list.add(seq);
		}
		for (int i = 0; i < dstList.size(); i ++) {
			FalconLangAst assign = new FalconLangAst(FalconLangAst.ASSIGN);
			assign.child1 = dstList.get(i);
			if (srcList != null) {
				// 变量赋值
				assign.child2 = srcList.get(i);
			} else {
				// 调用返回值赋值
				assign.child2 = new FalconLangAst(FalconLangAst.VALUE_TMP);
				String value = Integer.toString(i);
				assign.child2.token = new FalconLangToken(FalconLangToken.STRING, value);
			}
			FalconLangAst seq = new FalconLangAst(FalconLangAst.SEQ);
			seq.child1 = assign;
			list.add(seq);
		}
		FalconLangAst seq = processSeq(list);
		FalconLangAst.copy(seq, ast);
		return Errno.SUCCESS;
	}
	
	/**
	 * [invoke] -> . name ( [arg] )<br>
	 */
	private int processInvoke(FalconLangAst ast) {
		if (!check(0, FalconLangToken.OPERATOR, Operator.REF)) {
			Log.log("");
			return Errno.SCRIPT_SYNTAX_ERROR;
		}
		FalconLangToken name = peek(1);
		if (name == null || name.type != FalconLangToken.IDENT) {
			Log.log("");
			return Errno.SCRIPT_SYNTAX_ERROR;
		}
		ast.token = name;
		if (!check(2, FalconLangToken.SYMBOL, Symbol.LPAR)) {
			Log.log("");
			return Errno.SCRIPT_SYNTAX_ERROR;
		}
		move(3);
		ArrayList<FalconLangAst> list = new ArrayList<FalconLangAst>();
		int errno = processArg(list);
		if (errno != Errno.SUCCESS) {
			Log.log("");
			return errno;
		}
		if (list.size() > FalconLangProcessor.INVOKE_ARG_COUNT) {
			Log.log("");
			return Errno.SCRIPT_SYNTAX_ERROR;
		}
		ast.child1 = new FalconLangAst(FalconLangAst.VALUE_CONST);
		String value = Integer.toString(list.size());
		ast.child1.token = new FalconLangToken(FalconLangToken.STRING, value);
		ast.child2 = processSeq(list);
		if (!check(0, FalconLangToken.SYMBOL, Symbol.RPAR)) {
			Log.log("");
			return Errno.SCRIPT_SYNTAX_ERROR;
		}
		next();
		return Errno.SUCCESS;
	}
	
	/**
	 * [ret] -> [val]<br>
	 * [ret] -> <br>
	 */
	private int processReture(FalconLangAst ast) {
		if (peek() == null) {
			ast.child1 = new FalconLangAst(FalconLangAst.VALUE_CONST);
			ast.child1.token = new FalconLangToken(FalconLangToken.STRING, "0");
			return Errno.SUCCESS;
		}
		ArrayList<FalconLangAst> srcList = new ArrayList<FalconLangAst>();
		int errno = processVal(srcList);
		if (errno != Errno.SUCCESS) {
			return errno;
		}
		ArrayList<FalconLangAst> list = new ArrayList<FalconLangAst>();
		for (int i = 0; i < srcList.size(); i ++) {
			FalconLangAst assign = new FalconLangAst(FalconLangAst.ASSIGN);
			assign.child1 = new FalconLangAst(FalconLangAst.VALUE_TMP);
			String value = Integer.toString(i);
			assign.child1.token = new FalconLangToken(FalconLangToken.STRING, value);
			assign.child2 = srcList.get(i);
			FalconLangAst seq = new FalconLangAst(FalconLangAst.SEQ);
			seq.child1 = assign;
			list.add(seq);
		}
		ast.child1 = new FalconLangAst(FalconLangAst.VALUE_CONST);
		String value = Integer.toString(list.size());
		ast.child1.token = new FalconLangToken(FalconLangToken.STRING, value);
		ast.child2 = processSeq(list);
		return Errno.SUCCESS;
	}

	/**
	 * [condition] -> [logic] then<br>
	 */
	private int processCondition(FalconLangAst ast) {
		// 简化抽象语法树
		// 固定 if/elif -> and/or -> ==/!= 的层级关系
		ast.child1 = new FalconLangAst();
		int errno = processLogic(ast.child1);
		if (errno != Errno.SUCCESS) {
			Log.log("");
			return errno;
		}
		if (!check(0, FalconLangToken.KEYWORD, Keyword.THEN)) {
			Log.log("");
			return Errno.SCRIPT_SYNTAX_ERROR;
		}
		next();
		return Errno.SUCCESS;
	}
	
	/**
	 * [var] -> ident , [var]<br>
	 * [var] -> ident<br>
	 */
	private int processVar(ArrayList<FalconLangAst> list) {
		if (!check(0, FalconLangToken.IDENT, null)) {
			Log.log("");
			return Errno.SCRIPT_SYNTAX_ERROR;
		}
		FalconLangAst ast = new FalconLangAst(FalconLangAst.VALUE_VAR);
		ast.token = peek();
		list.add(ast);
		if (!check(1, FalconLangToken.SYMBOL, Symbol.COMMA)) {
			next();
			return Errno.SUCCESS;
		}
		move(2);
		return processVar(list);
	}
	
	/**
	 * [arg] -> [join] , [arg]<br>
	 * [arg] -> [join]<br>
	 */
	private int processArg(ArrayList<FalconLangAst> list) {
		FalconLangAst child2 = new FalconLangAst();
		int errno = processJoin(child2);
		if (errno != Errno.SUCCESS) {
			Log.log("");
			return errno;
		}
		FalconLangAst pass = new FalconLangAst(FalconLangAst.PASS);
		pass.child1 = new FalconLangAst(FalconLangAst.VALUE_TMP);
		String value = Integer.toString(list.size());
		pass.child1.token = new FalconLangToken(FalconLangToken.STRING, value);
		pass.child2 = child2;
		FalconLangAst ast = new FalconLangAst(FalconLangAst.SEQ);
		ast.child1 = pass;
		list.add(ast);
		if (!check(0, FalconLangToken.SYMBOL, Symbol.COMMA)) {
			return Errno.SUCCESS;
		}
		next();
		return processArg(list);
	}

	/**
	 * [val] -> [join] , [val]<br>
	 * [val] -> [join]<br>
	 */
	private int processVal(ArrayList<FalconLangAst> list) {
		FalconLangAst ast = new FalconLangAst();
		int errno = processJoin(ast);
		if (errno != Errno.SUCCESS) {
			Log.log("");
			return errno;
		}
		list.add(ast);
		if (!check(0, FalconLangToken.SYMBOL, Symbol.COMMA)) {
			return Errno.SUCCESS;
		}
		next();
		return processVal(list);
	}
	
	/**
	 * [logic] -> [compare] and [logic]<br>
	 * [logic] -> [compare] or [logic]<br>
	 * [logic] -> [compare]<br>
	 */
	private int processLogic(FalconLangAst ast) {
		FalconLangAst node = new FalconLangAst();
		int errno = processCompare(node);
		if (errno != Errno.SUCCESS) {
			Log.log("");
			return errno;
		}
		ast.child1 = node;
		ast.type = FalconLangAst.LOGIC_AND;
		if (check(0, FalconLangToken.KEYWORD, Keyword.OR)) {
			ast.type = FalconLangAst.LOGIC_OR;
		} else if (!check(0, FalconLangToken.KEYWORD, Keyword.AND)) {
			return Errno.SUCCESS;
		}
		next();
		ast.child2 = new FalconLangAst();
		return processLogic(ast.child2);
	}
	
	/**
	 * [compare] -> [join] == [join]<br>
	 * [compare] -> [join] != [join]<br>
	 * [compare] -> [join] > [join]<br>
	 * [compare] -> [join] >= [join]<br>
	 * [compare] -> [join] < [join]<br>
	 * [compare] -> [join] <= [join]<br>
	 */
	private int processCompare(FalconLangAst ast) {
		FalconLangAst node = new FalconLangAst();
		int errno = processJoin(node);
		if (errno != Errno.SUCCESS) {
			Log.log("");
			return errno;
		}
		int type;
		if (check(0, FalconLangToken.OPERATOR, Operator.EQ)) {
			type = FalconLangAst.COMPARE_EQ;
		} else if (check(0, FalconLangToken.OPERATOR, Operator.NE)) {
			type = FalconLangAst.COMPARE_NE;
		} else if (check(0, FalconLangToken.OPERATOR, Operator.GT)) {
			type = FalconLangAst.COMPARE_GT;
		} else if (check(0, FalconLangToken.OPERATOR, Operator.GE)) {
			type = FalconLangAst.COMPARE_GE;
		} else if (check(0, FalconLangToken.OPERATOR, Operator.LT)) {
			type = FalconLangAst.COMPARE_LT;
		} else if (check(0, FalconLangToken.OPERATOR, Operator.LE)) {
			type = FalconLangAst.COMPARE_LE;
		} else {
			Log.log("");
			return Errno.SCRIPT_SYNTAX_ERROR;
		}
		next();
		ast.type = type;
		ast.child1 = node;
		ast.child2 = new FalconLangAst();
		return processJoin(ast.child2);
	}
	
	/**
	 * [join] -> [expr] ~ [join]<br>
	 * [join] -> [expr]<br>
	 */
	private int processJoin(FalconLangAst ast) {
		FalconLangAst node = new FalconLangAst();
		int errno = processExpr(node);
		if (errno != Errno.SUCCESS) {
			Log.log("");
			return errno;
		}
		if (!check(0, FalconLangToken.OPERATOR, Operator.JOIN)) {
			FalconLangAst.copy(node, ast);
			return Errno.SUCCESS;
		}
		next();
		ast.type = FalconLangAst.OPERATOR_JOIN;
		ast.child1 = node;
		ast.child2 = new FalconLangAst();
		return processJoin(ast.child2);
	}
	
	/**
	 * [expr] -> [term] + [expr]<br>
	 * [expr] -> [term] - [expr]<br>
	 * [expr] -> [term]<br>
	 */
	private int processExpr(FalconLangAst ast) {
		FalconLangAst node = new FalconLangAst();
		int errno = processTerm(node);
		if (errno != Errno.SUCCESS) {
			Log.log("");
			return errno;
		}
		int type;
		if (check(0, FalconLangToken.OPERATOR, Operator.ADD)) {
			type = FalconLangAst.OPERATOR_ADD;
		} else if (check(0, FalconLangToken.OPERATOR, Operator.SUB)) {
			type = FalconLangAst.OPERATOR_SUB;
		} else {
			FalconLangAst.copy(node, ast);
			return Errno.SUCCESS;
		}
		next();
		ast.type = type;
		ast.child1 = node;
		ast.child2 = new FalconLangAst();
		return processExpr(ast.child2);
	}
	
	/**
	 * [term] -> [factor] * [term]<br>
	 * [term] -> [factor] / [term]<br>
	 * [term] -> [factor] % [term]<br>
	 * [term] -> [factor]<br>
	 */
	private int processTerm(FalconLangAst ast) {
		FalconLangAst node = new FalconLangAst();
		int errno = processFactor(node);
		if (errno != Errno.SUCCESS) {
			Log.log("");
			return errno;
		}
		int type;
		if (check(0, FalconLangToken.OPERATOR, Operator.MUL)) {
			type = FalconLangAst.OPERATOR_MUL;
		} else if (check(0, FalconLangToken.OPERATOR, Operator.DIV)) {
			type = FalconLangAst.OPERATOR_DIV;
		} else if (check(0, FalconLangToken.OPERATOR, Operator.MOD)) {
			type = FalconLangAst.OPERATOR_MOD;
		} else {
			FalconLangAst.copy(node, ast);
			return Errno.SUCCESS;
		}
		next();
		ast.type = type;
		ast.child1 = node;
		ast.child2 = new FalconLangAst();
		return processTerm(ast.child2);
	}
	
	/**
	 * [factor] -> ( [expr] )<br>
	 * [factor] -> [value]<br>
	 */
	private int processFactor(FalconLangAst ast) {
		if (!check(0, FalconLangToken.SYMBOL, Symbol.LPAR)) {
			return processValue(ast);
		}
		next();
		int errno = processExpr(ast);
		if (errno != Errno.SUCCESS) {
			Log.log("");
			return errno;
		}
		if (!check(0, FalconLangToken.SYMBOL, Symbol.RPAR)) {
			return Errno.SCRIPT_SYNTAX_ERROR;
		}
		next();
		return Errno.SUCCESS;
	}
	
	/**
	 * [value] -> {string}<br>
	 * [value] -> {param}<br>
	 * [value] -> {ident}<br>
	 * [value] -> sys [invoke]<br>
	 */
	private int processValue(FalconLangAst ast) {
		if (check(0, FalconLangToken.STRING, null)) {
			ast.type = FalconLangAst.VALUE_CONST;
		} else if (check(0, FalconLangToken.ARG, null)) {
			ast.type = FalconLangAst.VALUE_ARG;
		} else if (check(0, FalconLangToken.IDENT, null)) {
			ast.type = FalconLangAst.VALUE_VAR;
		} else if (check(0, FalconLangToken.KEYWORD, FalconLangToken.Keyword.SYS)) {
			next();
			ast.type = FalconLangAst.INVOKE_SYS;
			return processInvoke(ast);
		} else {
			Log.log("");
			return Errno.SCRIPT_SYNTAX_ERROR;
		}
		ast.token = peek();
		next();
		return Errno.SUCCESS;
	}
	
	private FalconLangToken peek(int offset) {
		int i = m_x + offset;
		if (i < 0 || i >= m_tokenList.size()) {
			return null;
		}
		return m_tokenList.get(i);
	}
	
	private FalconLangToken peek() {
		return peek(0);
	}
	
	private boolean check(int offset, int type, String value) {
		FalconLangToken token = peek(offset);
		if (token == null) {
			return false;
		}
		if (value == null) {
			return token.type == type;
		}
		return token.check(type, value);
	}
	
	private void move(int offset) {
		m_x += offset;
	}
	
	private void next() {
		move(1);
	}
	
	private boolean checkIfBlock(int type) {
		if (m_stack.isEmpty()) {
			if (type == FalconLangAst.CONDITION_IF) {
				m_stack.push(FalconLangAst.CONDITION_IF);
				return true;
			}
			return false;
		}
		if (type == FalconLangAst.CONDITION_END) {
			while (!m_stack.isEmpty() && m_stack.peek() != FalconLangAst.CONDITION_IF) {
				m_stack.pop();
			}
			return true;
		}
		if (m_stack.peek() == FalconLangAst.CONDITION_ELSE) {
			return false;
		}
		m_stack.push(type);
		return true;
	}
	
	private static FalconLangAst processSeq(ArrayList<FalconLangAst> list) {
		if (list == null || list.isEmpty()) {
			Log.log("");
			return null;
		}
		FalconLangAst head = list.get(0);
		FalconLangAst current = head;
		for (int i = 1; i < list.size(); i ++) {
			FalconLangAst next = list.get(i);
			current.child2 = next;
			current = next;
		}
		return head;
	}
	
	private int m_x;
	private ArrayList<FalconLangToken> m_tokenList;
	private Stack<Integer> m_stack;
}
